package com.atguigu.gulimall.cart.service.serviceImpl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.utils.R;
import com.atguigu.gulimall.cart.feign.ProductFeignService;
import com.atguigu.gulimall.cart.interceptor.CartInterceptor;
import com.atguigu.gulimall.cart.service.CartService;
import com.atguigu.gulimall.cart.vo.Cart;
import com.atguigu.gulimall.cart.vo.CartItem;
import com.atguigu.gulimall.cart.vo.SkuInfoVo;
import com.atguigu.gulimall.cart.vo.UserInfoTo;
import com.fasterxml.jackson.databind.util.BeanUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

@Service
@Slf4j
public class CartServiceImpl  implements CartService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private ThreadPoolExecutor threadPool;

    //不论是临时购物车还是登陆后的购物车，我们都需要在redis中为购物车加上前缀，这是redis中的命名规范
    private final String CART_PREFIX = "gulimall:cart:";

    @Resource
    private ProductFeignService productFeignService;

    @Override
    public Cart getCartData() throws ExecutionException, InterruptedException {
        Cart cart = new Cart();

        //1.查看时获取哪个购物车，登陆后的还是临时购物车
        UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
        //2.说明登陆了,但购物车可能需要合并
        if (userInfoTo.getUserId() != null) {
            List<CartItem> tempCartItems = getCartItems(userInfoTo.getUserKey());
            if (tempCartItems != null) {     //说明临时购物车有数据，需要合并
                for (CartItem item : tempCartItems) {
                    addToCart(item.getSkuId(), item.getCount());
                }
                //合并完成后需要清除临时购物车的数据
                clearCart(CART_PREFIX + userInfoTo.getUserKey());
            }
            //3.合并之后再来获取购物车的数据【可能包含临时购物车数据】
            List<CartItem> cartItems = getCartItems(userInfoTo.getUserId().toString());
            cart.setItems(cartItems);
        } else {
            //获取临时购物车的所有购物项
            List<CartItem> cartItems = getCartItems(CART_PREFIX + userInfoTo.getUserKey());
            if (cartItems != null && cartItems.size() > 0) {
                cart.setItems(cartItems);
            }
        }
        return cart;
    }

    private List<CartItem> getCartItems(String cartKey) {
        String redisKey = CART_PREFIX + cartKey;
        BoundHashOperations<String, Object, Object> hashOps =
                stringRedisTemplate.boundHashOps(redisKey);
        List<Object> list = hashOps.values();
        if (list != null && list.size() > 0) {
            List<CartItem> cartItems = list.stream().map((item) -> {
                CartItem cartItem = JSON.parseObject(item.toString(), CartItem.class);
                return cartItem;
            }).collect(Collectors.toList());
            return cartItems;
        }
        return null;
    }


    @Override
    public CartItem addToCart(Long skuId, Integer num) throws ExecutionException, InterruptedException {
        //1.拿到购物车(不管是临时购物车还是登陆后的购物车)
        BoundHashOperations<String, Object, Object> cart = getCart();
        CartItem cartItem = new CartItem();
        //在接下来的操作之前我们需要先判断是否添加的是新商品，还是仅仅是将已经在购物车中的
        //购物项改变一下数量
        String s = (String) cart.get(skuId.toString());
        if(StringUtils.isEmpty(s)){
            //2.将“新”商品添加到购物车
            CompletableFuture<Void> getSkuInfoTask = CompletableFuture.runAsync(() -> {
                R info = productFeignService.info(skuId);
                SkuInfoVo skuInfo = info.getData("skuInfo", new TypeReference<SkuInfoVo>() {
                });
                cartItem.setSkuId(skuId);
                cartItem.setCheck(true);
                cartItem.setCount(num);
                cartItem.setImage(skuInfo.getSkuDefaultImg());
                cartItem.setTitle(skuInfo.getSkuTitle());
                cartItem.setPrice(skuInfo.getPrice());
            },threadPool);

            //3.远程查询sku的组合信息,为此我们需要去gulimall-product服务里面写一个方法
            //路径是com.atguigu.gulimall.product.app.SkuSaleAttrValueController
            //方法名为：getSkuSaleAttrValues()
            CompletableFuture<Void> getSkuSaleAttrValuesTask = CompletableFuture.runAsync(() -> {
                cartItem.setSkuAttr(productFeignService.getSkuSaleAttrValues(skuId));
            }, threadPool);

            //我们需要等两个异步任务都完成后才封装数据，否则会出现空数据带来的异常
            CompletableFuture.allOf(getSkuInfoTask,getSkuSaleAttrValuesTask).get();
            //4.最后我们拿到了购物项的所有信息后，我们就需要向redis中存储信息
            String jsonString = JSON.toJSONString(cartItem);
            cart.put(skuId.toString(),jsonString);
            return cartItem;
        }
        else {
            CartItem item = JSON.parseObject(s, CartItem.class);
            item.setCount(item.getCount()+num);
            String string = JSON.toJSONString(item);
            cart.put(skuId.toString(),string);
            return item;
        }
    }

    @Override
    public CartItem getCartItem(Long skuId) {
        BoundHashOperations<String, Object, Object> cart = getCart();
        String dataFromRedis = (String)cart.get(skuId.toString());
        CartItem cartItem = JSON.parseObject(dataFromRedis, CartItem.class);
        return cartItem;
    }

    /**
     * 因为在同一个请求拦截后的业务中，我们每次都需要对于同一个名字的hash进行操作，
     * 为了避免redis中hash每次都得输入名字，我们可以使用BoundHashOperations这个类
     * 它绑定了同一个名字，简化的代码的书写。
     * 同时，我们也将拿到购物车(不管是临时购物车还是登陆后的购物车)的代码抽取成了一个方法
     * @return
     */
    private BoundHashOperations<String, Object, Object> getCart() {
        //对于我们要把购物项存到临时购物车还是登陆后的购物车，就需要我们通过拦截器获取到状态
        UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
        String cartKey = "";
        //如果To对象里面有用户id，说明用户登陆了，登录的话就需要使用登陆后的购物车
        if(userInfoTo.getUserId()!=null){
            //gulimall:cart:1
            cartKey = CART_PREFIX + userInfoTo.getUserId();
        }else {
           //否则的话就用临时用户的购物车
            cartKey = CART_PREFIX + userInfoTo.getUserKey();
        }
        BoundHashOperations<String, Object, Object> hashOps = stringRedisTemplate.boundHashOps(cartKey);
        return hashOps;
    }

    @Override
    public void clearCart(String cartKey){
        stringRedisTemplate.delete(cartKey);
    }


    @Override
    public List<CartItem> getUserCartItems() {
        BoundHashOperations<String, Object, Object> cart = getCart();
        List<CartItem> collect = cart.values().stream().map((item) -> {
            CartItem cartItem = JSON.parseObject(item.toString(), CartItem.class);
            //下面的远程调用我们不写了
            //因为我们之前存入redis中的商品价格不可能一成不变，因此我们需要对商品的price属性重新赋值(就像房子涨价了的)
            //这就需要我们得再次查询gulimall-product的对应sku的价格进行重新逐注入
            //因为很无聊，我们就直接返回，不做了
            return cartItem;
        }).filter((item) ->{
            return item.getCheck() == true;
        }).collect(Collectors.toList());

        return collect;
    }
}
